package Cluedo.AI;

import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Vector;

import Cluedo.API.CluedoConfig;
import Cluedo.API.CluedoListener;
import Cluedo.API.DisproofAlert;
import Cluedo.API.Message;
import Cluedo.API.SuggestionAlert;
import Cluedo.Controller.Controller;
import Cluedo.Game.Card;
import Cluedo.Game.CardCollection;
import Cluedo.Game.Map;
import Cluedo.Game.Player;
import Cluedo.Game.Position;
import Cluedo.Game.Room;
import Cluedo.Game.Square;
import Cluedo.Game.StartingSquare;
 
/**
 * AI interacts with the game by listening to messages from the GameController
 * and then sending messages back to make changes.<p>
 * 
 * First Created on Dec 12, 2004
 * 
 * @author Alex Ellis
 * 
 */
public class SimpleAI implements 
	Runnable, Cluedo.API.AIContract {

	static int sleepPeriod = 10;
	
	/**
	 * This identifies the AI, so it can decide when it's his turn to respond to input.
	 */
	String myName = "";
	
	/**
	 * If set to true the AI's run() function will end.
	 */
	boolean dead = false;

	/**
	 * Stores all incoming messages in a queue. This AI is a thread, therefore it receives messages into
	 * a queue and parses them from its run() function.
	 */
	Vector inbox = new Vector();
	
	/**
	 * Current behaviour of the AI
	 */
	String motive = "";
	/**
	 * The room the AI wants to get to, intermediate Squares aren't stored here.
	 */
	Position destination = null;
	
	
	/**
	 * Detective if we were moved by a suggest
	 * 
	 */
	Position lastPos = null;

	/**
	 * Indicates a disproof to the AI's suggestion was received.
	 */
	boolean disproofReceived = true;
	
	/**
	 * Used to store a disproof to the AI's suggestion.
	 */
	Message incomingDisproof = null;
	
	/**
	 * List of motives that an AI may take.
	 *
	 */
	String [] motives = { "findRoom", "walkToRoom",};
	
	/**
	 * CluedoListener acts as an inbox to receive messages from the GameController and will decide
	 * whether it needs to update any information.
	 *
	 */
	CluedoListener cluedoListener = new CluedoListener() {
		/**
		 * Messages here are received from the TextHandler and result in some action
		 * being taken locally in this View, commands called from here are located in
		 * an inner-class called LocalEvents.<br>
		 * The return value from the local event is outputted to the user.
		 *
		 */
		public void localMessage(Cluedo.API.Message message) {}
	
		/** 
		* Receives messages from the GameController and handles them in GameEvents
		*
		*/
		public void notifyAlert(Message message) {
			String output = "";
			if(debugLevel >= 2)
				outputMessage("[SimpleAI:notifyAlert] [" + message.type + "] '" + message.data + "'\n");

			
		
			// One case where we need a little help..
			if(disproofReceived == false) {
				if(message.type.equals("disproofAlert")) {
					disproofReceived = true;
					incomingDisproof = message;
					inbox.add(message);
					
				}

			}
			else
				inbox.add(message);
			
		}
        	
        	
		/* End code that handles the interaction of the GUI
		 * 
		 */
		public CluedoListener getCluedoListener() {
			return this;
		}
	
	};

	public CluedoListener getCluedoListener () {
		return cluedoListener;
	}


	private void sleepFor(int interval) {
		try {
			Thread.sleep(interval);
			outputMessage("\nPausing execution.. ("+interval+") ms\n");
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Construct a new simple AI, and bind itself to the Controller to receive game messages.
	 *
	 * @param args Does nothing
	 * @param gameController2 A pointer/link to the Controller.
	 * @param AIName Name that uniquely identifes this AI player
	 */
	public SimpleAI (String args, Controller gameController, String AIName) {
		//super(args);
		myName = AIName;
		this.gameController = gameController;
		System.out.println("[SimpleAI:] GC="+this.gameController.toString() + ", NAME=" + myName);
		bindAI();
		motive = "findRoom";
	}

	/**
	 * AI will bind its CluedoListener to the gameController such that it will receive game messages.
	 */
	public void bindAI() {
		System.out.println("[SimpleAI2:BindAI] binding to listener " + getCluedoListener());
		try {
			gameController.bindListener(getCluedoListener());
		}
		catch(Exception Error) {
			System.out.println("[SimpleAI2:BindAI] can't bind");
		}
	}
	
	/**
	 * If called thread will exit run() method. Called when game ends.
	 */
	synchronized public void die() {
		dead = true;
	}
	
	/**
	 * Main AI loop, will stop if die() is called.
	 *
	 * Removes message from inbox and processes it in handleMessage
	 *
	 */
	public void run () {
		while( dead == false ) {
			try {

				try {
					Thread.sleep(100);
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}

				if(inbox.size() > 0) {
					while(inbox.size() > 0) {
						Thread.sleep(10);
						try {
							handleMessage(((Message)inbox.firstElement()));
						}
						catch(Exception unknown) {
							unknown.printStackTrace();
						}
						inbox.remove(inbox.firstElement());
					}
				}
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	boolean printedMyCards = false;
	
	/**
	 * A message is taken from the queue and given to the AI, he decides what to do about that.
	 * @param message Message from the Q.
	 *
	 */
	 public synchronized void handleMessage(Message message) {
		String output = "";

		
		if(printedMyCards == false) {
			System.out.println(myName +"\n"+ gameController.getBoard().getPlayer(myName).getCards().toXML());
			printedMyCards = true;
		}
		
		
		if(debugLevel > 5)
			outputMessage("handleMessage " + message.type);
		if(gameController.getBoard().getGameFinished() == false) {
			//if(message.type.equals("commenceGame"))
				//message.type = "nextPlayer";
				
			if(message.type.equals("newRoll")) {
                	
			}
			if(message.type.equals("commenceGame")) {
				message.type = "nextPlayer";
				outputMessage("We're all waiting a little bit for the game code to warm-up");
				sleepFor(sleepPeriod*10);
				

			}
			else if(message.type.equals("newEnvelope")) {
                	
			}
			else if(message.type.equals("suggestionAlert")) {
				if(message.data.equals(myName)) {
					
					handleSuggestionAlert(message);
				}
				
			}
			else if(message.type.equals("disproofAlert")) {
					outputMessage("\nHeard a disproofAlert ...\n");
					disproofReceived = true;
                	
			}
			else if(message.type.equals("winGame")) {
			    outputMessage("Game ends.", 1);
			    dead = true;
			    return;
			}
			else if(message.type.equals("gameOver")) {
			    outputMessage("Game ends.", 1);
			    dead = true;
			    return;
			}
			if(message.type.equals("nextPlayer")) {

				if(gameController.getBoard().getCurrentPlayer().getRealName().equals(myName)) {
//					outputMessage(buildCards());
					outputMessage(" ********************************************");
					outputMessage(" **** "+gameController.getBoard().getCurrentPlayer().getRealName()+" ****");
					outputMessage(" ********************************************");
					output = "\n";

					try {
					    takeTurn();
					}
					catch(Exception exp) {
					    exp.printStackTrace();
					}
					
//					outputMessage(buildCards());
				}
			}
			else if(message.type.equals("moveAlert")) {
                	
			}		
		}
		else {
			if(dead == false) {
				outputMessage("Game is over and i'm quitting..");
				die();
			}
		}
		if(!output.equals(""))
			outputMessage(output);
		
	}

	/**
     * @param string
     * @param i
     */
    private void outputMessage(String output, int i) {
        if(i == 1) {
		StringTokenizer lines = new StringTokenizer(output, "\n");
		while ( lines.hasMoreElements() == true ) {
			System.err.println("[AI:"+myName+"] " + lines.nextToken() );
		}
		System.err.println();
        }
    }


    /**
	 * Manually set the AI name, see constructor
	 */
	public void setMyName(String myName) {
		this.myName = myName;
	}

	/**
	 * Only prints a debug
	 */
	public void initaliseAI() {
		System.out.println("[SimpleAI2:initaliseAI] Initalised "+myName+".");
	}

	
	/**
	 *  Start code that handles the interaction of the AI
	 * 
	 */
	
	/** The game controller for this View */
	Controller gameController = null;
	/** The parser for text input from this View */
	Cluedo.GUI.TextParser textParser;
	/**  The handler for input from the parser on this View */
	Cluedo.GUI.TextEventHandler textHandler = new Cluedo.GUI.TextEventHandler(this);
	/** Create a game config class to read in and sort out what the new game will be like */
	CluedoConfig gameConfig = null;
	int debugLevel = 0;
	

	/**
	 * Debug function makes clear which AI is "talking"
	 */
	synchronized void outputMessage(String output ) {
		StringTokenizer lines = new StringTokenizer(output, "\n");
		while ( lines.hasMoreElements() == true ) {
			System.out.println("[AI:"+myName+"] " + lines.nextToken() );
		}
		System.out.println();
	}


	/**
	 * Figures out what to do with a message of type ='suggestionAlert'.<p>
         *
	 * Searches cards in suggestionAlert until it finds one it has in its
	 * set of cards, then composes disproofAlert alert as a reply to the
	 * message and relays this through the controller.
	 *
	 */
	private void handleSuggestionAlert(Message message) {

		outputMessage("\nHeard a suggestionAlert directed at me...\n");
		Message disproofAlert = new Message();
		String chosenCard = "";
		
		disproofAlert.type = "disproofAlert";
		
		SuggestionAlert alert = (SuggestionAlert)message.parameters.elementAt(0);
		Player me = gameController.getBoard().getPlayer(myName);
		
		
		if(me.getCards().containsCard(alert.getWhat()))
			chosenCard = alert.what.getName();
		else if(me.getCards().containsCard(alert.getWhere()))
			chosenCard = alert.where.getName();
		else if(me.getCards().containsCard(alert.getWho()))
			chosenCard = alert.who.getName();
			
		// If this happens, there is a bug in the system.
		if(chosenCard.equals(""))
			outputMessage("I didn't have a card to disprove with and yet I was asked to disprove!");

		//DisproofAlert dalert = new DisproofAlert(chosenCard, alert);
		DisproofAlert dalert = new DisproofAlert(chosenCard, alert, myName);
		disproofAlert.parameters.add(dalert);

		gameController.relayMessage(disproofAlert);
		
		outputMessage("\nRelay of disproofAlert... Ok\n");

	}

	/**
	 * Clean up anything and notify gameController to allow next player to play.
	 *
	 */
	synchronized public String endGo() throws Exception {
		String output = "";
		Vector empty = new Vector();
		
		gameController.directCommand("nextPlayer", empty);

		return output;
	}

	/**
	 * Function for debug, returns string status of all cards.
	 *
	 * @return String containing status of all cards.
	 */
	synchronized public String buildCards() {
		StringBuffer buffer = new StringBuffer();
		
		Player me = gameController.getBoard().getPlayer(myName);
		CardCollection myCards = me.getCards();
		CardCollection myDCards = me.getDetectivePad().getCardList();
		
		for(int i = 0; i < myDCards.countCards(); i++) {
			buffer.append("- " + myDCards.cardAt(i).getName() +"\t" + myDCards.cardAt(i).isEliminated()+"\n");
		}
		
		
		return buffer.toString();
	}

	/**
	 * Called if after a nextPlayer alert if getBoard().getCurrentPlayer().getRealName() == me<p>
	 *
	 * AI player will decide what he needs to do on this turn.
	 *
	 */
	synchronized public void takeTurn() throws Exception {
		String output = "";
		boolean deadlock = inDeadlock();
		
		outputMessage("takeTurn..");

		/** Firstly make a small thinking pause */
		sleepFor(sleepPeriod);
	
		/** Get the bits we need for the turn. */
		Player me = gameController.getBoard().getPlayer(myName);
		CardCollection myCards = me.getCards();
		CardCollection myDCards = me.getDetectivePad().getCardList();
		
		System.out.println("\t\tinDeadlock() "+inDeadlock());
			
			
		/**
		 * Check if an accusation should be made.
		 *
		 */
		if(shouldAccuse() == true) {
			boolean wonGame = makeAccusation();
			
			if(wonGame == true) {
				Vector arguments = new Vector();
				arguments.add(me);
				gameController.directCommand("winGame", arguments);	
			}
			else {
				gameController.directCommand("removeInteractive", null);
			}
			
			System.out.println("My accusation was " + wonGame);

			dead=true;
			return;
		}
		
		output += "I have to eliminate " + myDCards.getUneliminatedCards().size() + " / ("+myDCards.countCards()+") cards.\n";

		outputMessage(output);

		
		/**
		 * If we get moved on the last turn then we have to find the nearest room again
		 */
/*		if(lastPos != null && (motive.equals("walkToRoom") && lastPos != me.getPosition())) {
			motive = "findRoom";
			System.out.println("I got moved last turn.");
		}*/
		

		/**
		 * If not in the process of moving to a room, find a room that we want to visit.
		 */
		if(motive.equals("findRoom")) {
			destination = findRoom();
			System.out.println("Finding room: " + destination);
			motive = "walkToRoom";
		}
		
		sleepFor(sleepPeriod);
		
		/**
		 * If I'm supposed to find my way to the room then I'll start doing that.
		 * 
		 */
		if(deadlock == false) {
			if(motive.equals("walkToRoom")) {
				if( destination == null || destination.equals(me.getPosition())) {
					System.out.println("This current pos is the nearest!?");
					destination = findFreeNeighbour(me.getPosition());
				}
				if(destination != null) {
					walkToRoom(destination);
				}
				else {
					motive = "findRoom";
				}
			}
		}
		
		sleepFor(sleepPeriod);

		/**
		 * If it's possible to make a suggestion after the turn, then do so.
		 *
		 */		
		if(shouldSuggest() == true) {
    			output += suggest();
		}

		if(destination == me.getPosition()) {
			outputMessage("Arrvied at destination");
			motive = "findRoom";
			
		}
		else {
			outputMessage("Still in transit");
		}
		
		//lastPos = me.getPosition();
		
		/**
		 * Now end the go and let the next player have his go.
		 */
		try {
			outputMessage("calling endGo()");
			endGo();
			outputMessage("endGo() returned");
		}
		catch(Exception e) {
			e.printStackTrace();
		}

		}
	
	/**
	 * Decides if the AI will make a suggestion where it is.
	 *
	 * Added mainly to disable conspiracy.
	 * 
	 * @return true if ok, false if not ok
	 */
	public boolean shouldSuggest() {
		boolean ok = true;
		Player me = gameController.getBoard().getCurrentPlayer();
		Position p = me.getPosition();
		
		if(me.getCards().containsCard(new Card(p.getID(), "Room")) == true)
			ok = false;
		
		if(me.canSuggestHere() == false || me.canSuggest() == false)
			ok = false;
		
		return ok;
	}
		
		
	/**
	 * Check for a free position in one of the neighbouring nodes.
	 *
	 * @return null if node doesn't have free neighbouring node, otherwise return first found node
	 */
	Position findFreeNeighbour(Position node) {
		Position found = null;
		
		for(int i = 0; i < node.getNeighbours().size(); i++) {
			if(((node.getNeighbours().elementAt(i)) instanceof Room == true) ||
					((Position)node.getNeighbours().elementAt(i)).isOccupied() == false) {
				found = (Position)node.getNeighbours().elementAt(i);
			}
		}
		
		return found;
	}
	
	/**
	 * Walk to destination.<p>
	 * 
	 * Try to get to the destination by whatever means neccessary, make suggestions along the
	 * way wherever permitted.
	 * 
	 * 
	 */
	synchronized public void walkToRoom (Position destination) {
		String walkInfo = "";
		String output = "";
		
		try {
			Player me = gameController.getBoard().getPlayer(myName);
			CardCollection myCards = me.getDetectivePad().getCardList();
			
			System.out.println("walkToRoom(" + destination.getID()+") at: " + me.getPosition().getID());

			//System.out.println("Before dice");
			
			int diceRoll = 0; 
			
			/**
			 * Call the roll-dice function to find out how many squares we can move
			 */
			try {
				diceRoll = rollDice();
			}
			catch(Exception e) {
				e.printStackTrace();
			}
			
			//System.out.println("After dice");
			sleepFor(sleepPeriod);
			
			System.out.println("+ Dice: " + diceRoll);
			
			/**
			 * Check if there is a legal path to the route 
			 */
			Vector legalRoute = gameController.getBoard().map.routeTo(me.getPosition(), destination);
			
			Vector illegalRoute = gameController.getBoard().map.illegalRouteTo(me.getPosition(), destination);
			
			Vector chosenRoute = new Vector(); 
			
			boolean wholePath = false;
			boolean legal = false;
			int roll = me.getMovesRemaining();

			int movesOnPath = 0;

			outputMessage("Illegal size: " + gameController.getBoard().map.illegalRouteTo(me.getPosition(), destination).size() + "\n");
			outputMessage("Legal size: " + gameController.getBoard().map.routeTo(me.getPosition(), destination).size() +"\n");
			
			/** If we can use special rule */
			if((gameController.getBoard().getGameType() == 1 && roll == 12 && destination instanceof Room)) {
				chosenRoute = new Vector();
				chosenRoute.add( me.getPosition() );
				chosenRoute.add( destination );
				
				legal = true;
				wholePath = true;
			}
			/** Can take a legal route in one go? ? */
			else if((legalRoute.size() <= roll) && legalRoute.size() > 0) {
				chosenRoute = legalRoute;
				wholePath = true;
			}
			/** If we only move part of the way along */
			else {
				int movesOnLegalPath = 0;
				int movesOnIllegalPath = 0;
				
				if(legalRoute.size() > 0) {
					movesOnLegalPath = validatePath(legalRoute, roll);
					System.err.println("\nMY PLACE = "+movesOnLegalPath+"\n");
				}

				movesOnIllegalPath = validatePath(illegalRoute, roll);

				try {
					System.err.println("\tmovesOnLegalPath " + movesOnLegalPath + " movesOnIllegalPath " + movesOnIllegalPath);
				}
				catch(Exception e) {
					e.printStackTrace();
				}
				
				if(movesOnLegalPath >= movesOnIllegalPath) {
					chosenRoute = legalRoute;
					movesOnPath = movesOnLegalPath; 
					System.err.println("LegalRoute " + movesOnPath);
				}
				else {
					chosenRoute = illegalRoute;
					 //"Chosen: " + chosenRoute + " illegal: " + illegalRoute);
					movesOnPath = movesOnIllegalPath-1;
					System.err.println("IllegalRoute " + movesOnPath);
				}
				
			}
			
			System.out.println("Chosen route size: " + chosenRoute.size());
			/*
			if(index == 1 && ( path.get(index) instanceof Square && ((Position)path.get(index)).isOccupied() == true)) {
			    index = 1;
			    path = v;
			    if(v.size() == 0)
			    	return;
			}

			if(path.size() == index) {
				if(( path.get(index) instanceof Square && ((Position)path.get(index)).isOccupied() == true)) {
					path = v;
					index = v.size()-1;
				}
			}*/
			
			//System.out.println("wpath: "+wholePath + " index: " + index + " busy=" + ((Position)v.get(index)).isOccupied() + " " + ((Position)v.get(index)).getID());
			
			Position stopPoint = null;
			
			if(wholePath == true) {
				stopPoint = ((Position)chosenRoute.lastElement());
			}
			else {
				System.out.println(movesOnPath + " route: " + chosenRoute);
				stopPoint = ((Position)chosenRoute.get(movesOnPath-1));
			}
			
			System.err.println("Moving to "+ stopPoint.getID() + " " + chosenRoute + " wholePath " + wholePath + " m=" + movesOnPath);

			if(movesOnPath >= 1 || wholePath == true) {
			    movePlayer(stopPoint, chosenRoute);
			}
			else {
			    System.err.println("AI Confused");
			}

			/*
			if(legal == true) {
				movePlayer(((Position)v.get(v.size()-1)), v, v.size()-1);		    
			}
			else { 
				if(me.getPosition().getNeighbours().contains(destination)) {
					if(destination instanceof Square && destination.isOccupied() == true) {
						return;
					}else {
					    System.out.println("Moving to neighbour" + ((Position)v.get(v.size()-1)).getID());
						movePlayer(((Position)v.get(v.size()-1)), v, v.size()-1);		        
					}
				}
				else {
				    System.out.println("Moving to non-neighbour" + ((Position)path.get(index)).getID() + " index="+index);
					movePlayer(((Position)path.get(index)), path, index);
				}
			}*/
			
		}
		catch(Exception e) {
			e.printStackTrace();
		}

	}
/**
	 * @param chosenRoute
	 * @return
	 */
	private int validatePath(Vector chosenRoute, int rollAllowed) {
		int movesOnPath = 1;
		boolean wholePath = false;
		
		for(int i = 1; i < chosenRoute.size(); i++) {

			// Can't pass through positions
			if(((Position)chosenRoute.get(i)).isOccupied() == true && 
					((Position)chosenRoute.get(i)) instanceof Square) {
				wholePath = false;
				System.err.println("Break 1");
				break;
			}

			// Stop in a room
			if(((Position)chosenRoute.get(i)) instanceof Room) {
				wholePath = false;
				System.err.println("Break 2 " + ((Position)chosenRoute.get(i)).getID() +" " + movesOnPath);

			 //TODO: movesOnPath++; 
				movesOnPath++;
				        
				break;
			}
			
			// If the dice roll has been exceeded
			if(i >= rollAllowed) {
				wholePath = false;
				System.err.println("Break 3");
				break;
			}
			
			System.err.println("movesOnPath: "+movesOnPath);
			movesOnPath++;
		}

		return movesOnPath;
	}


//		
//		Position previousDestination = destination;
//		
//		if(v.size()-1 == -1) {
//			destination = null;
//			Vector rooms = gameController.getBoard().map.getNodes("rooms");
//			
//			for(int i = 0; i < rooms.size(); i++) {
//				v = gameController.getBoard().map.routeTo(me.getPosition(), (Position) rooms.elementAt(i));
//				System.out.println("Trying to get another room..");
//				if(v.size()-1 == -1) {
//					continue;
//				}
//				else {
//					if(previousDestination != destination) {
//						destination = (Position) rooms.elementAt(i);
//						break;
//					}
//				}
//
//
//			}
//			if(destination == null)
//				return;
//
//		}
//
//		walkInfo += "Hmm.. about "+(v.size()-1)+" moves, i'd say..\n";
//		walkInfo += "Starting at: ";
//		outputMessage(walkInfo);
//		
//		for(int i = 0; i < v.size(); i++)
//			walkInfo += " -> " + ((Position)v.elementAt(i)).getID();
//		
//		walkInfo += "\nWell, I'm allowed to make " + diceRoll+" moves, let's see about that\n";
//
//
//			motive = "findRoom";
//		}


//		/** 
//		 * If we're in the destination, then we can make the suggestion we wanted to make.
//		 * 
//		 */
//		if(me.getPosition() == destination ) {
//			motive = "findRoom";
//			output += "I got to my destination!!\n";
//		}
//        	
//		sleepFor(sleepPeriod);
//        	
//		/** 
//		 * If we're not yet in the destination, but in a room, why not make a suggestion here?
//		 * 
//		 */
//		if(me.getPosition() instanceof Room) {
//			output += "Erm.. I could make a quick suggestion here probably.\n";
//        	
//			outputMessage(output);
//		}
//	
//	}

	/**
	 * Finds a room for the AI to go to.<p>
	 * <li> - It goes to all uneliminated rooms based on which is the closest
	 * 
	 * @return a chosen destination
	 */
	synchronized public Position findRoom() {
		Position destination = null;
		try {
			
			Player me = gameController.getBoard().getPlayer(myName);
			CardCollection myDCards = me.getDetectivePad().getCardList();
			
			/**
			 *  Find all rooms in the game.
			 */
			Vector rooms = gameController.getBoard().map.getNodes("rooms");
			
			
			/** 
			 * Rooms to search
			 */
			Vector roomsToSearch = new Vector();
			
			for(int i=0;i<rooms.size();i++) {
				if(myDCards.cardByName( ((Room)rooms.get(i)).getID()).isEliminated() == false) {
					roomsToSearch.add(rooms.get(i));
				}
			}
			
			int distances[] = findDistances(roomsToSearch);
			int smallest = Integer.MAX_VALUE;
			int smallestIndex = -1;
			
			for(int i=0;i<distances.length; i++) {
				if(distances[i] <= smallest && distances[i] > 0) {
					smallest = distances[i];
					smallestIndex = i;
					
				}
			}
			
			StringBuffer latency = new StringBuffer();
			for(int i=0;i<roomsToSearch.size();i++) {
				latency.append(
						( (Room)roomsToSearch.get(i)).getID() +" =  "+ roomsToSearch.get(i).getClass().getName()  + "  (" +distances[i] +")\n" );
			}
			outputMessage(latency.toString());
			
			if(smallestIndex == -1)
				destination = (Room)roomsToSearch.get(0);
			else
				destination = (Room)roomsToSearch.get(smallestIndex);
			
			
			try {
				System.out.println("My aim is: " + destination.getID() + "\n" +
						"Illegal size: " + smallest + "\n" +
						"Legal size: " + gameController.getBoard().map.routeTo(me.getPosition(), destination).size() + "\n");
			}
			catch(Exception e) {
				e.printStackTrace();
				
			}
			if(gameController.getBoard().map.routeTo(me.getPosition(), destination).size() == 0 && 
					gameController.getBoard().map.illegalRouteTo(me.getPosition(), destination).size() == 0) {
				System.out.println("AI not allowed to move.");
				return null;
			}
		}
		catch(Exception e ) {
			e.printStackTrace();
		}
		
		return destination; 
	}

//		/**
//		 * If all the rooms were eliminated, then just pick one at random
//		 */
//		if(destination == null || randomFirst == true) {
//			int index = (int) (Math.random() * (rooms.size()));
//			outputMessage("index "+index);
//			destination = ( (Room)rooms.elementAt( index ) );
//		}
//		
//		outputMessage("My aim is: " + destination.getID() + ".\n");
//		
//		return destination; 
//	}

		
	/**
	 * @param roomsToSearch
	 * @return
	 */
	private int[] findDistances(Vector roomsToSearch) {
		int rooms[] = new int[roomsToSearch.size()];
		
		Player me = gameController.getBoard().getCurrentPlayer();
		Position src = gameController.getBoard().getCurrentPlayer().getPosition();
		Map map = gameController.getBoard().map;
		
		for(int i=0;i<roomsToSearch.size();i++) {
			int path = map.illegalRouteTo(src, (Position)roomsToSearch.get(i)).size();
			rooms[i] = path;
		}
				
		return rooms;
	}


	/**
	 * Finds if the AI should make an accusation.
	 * 
	 * Makes an accusation if there is 1 weapon, suspect, and room left uneliminated.
	 * 
	 * This method can be bypassed if a suggestion is not disproved and the player has
	 * none of the cards, this means inherently that the player has found the secret
	 * cards. 
	 * 
	 * @return accusation decision
	 */
	synchronized public boolean shouldAccuse() {
		boolean result = false;
		
		Card weapon = null;
		Card suspect = null;
		Card room = null;
		
		Player me = gameController.getBoard().getPlayer(myName);
		CardCollection myCards = me.getCards();
		CardCollection myDCards = me.getDetectivePad().getCardList();
		
		int numberOfWeapons = 0;
		int numberOfRooms = 0;
		int numberOfSuspects = 0;
		
		ArrayList cards = new ArrayList();
		
		for(int i = 0; i < myDCards.countCards(); i++) {
			if(myDCards.cardAt(i).isEliminated() == false) {
				if(myDCards.cardAt(i).getType().equals("Weapon"))
					numberOfWeapons++;
				else if(myDCards.cardAt(i).getType().equals("Suspect"))
					numberOfSuspects++;
				else if(myDCards.cardAt(i).getType().equals("Room"))
					numberOfRooms++;
				
				cards.add(myDCards.cardAt(i));
			}
		}
		
		System.out.println("\n\n\n\nshouldAccuse: "+numberOfWeapons + " " + numberOfSuspects + " " + numberOfRooms+"\n\n\n\n");
		
		for(int i =0;i<cards.size();i++)
			System.out.println(((Card)cards.get(i)).getName());
		
		result = (numberOfWeapons == 1 && numberOfSuspects == 1 && numberOfRooms == 1);
	
		return result;
	}

	/**
	 * Find 3 cards to use in an accusation then call the accuse() function.
	 * 
	 */
	synchronized public boolean makeAccusation() {
		Card weapon = null;
		Card suspect = null;
		Card room = null; 
		
		Player me = gameController.getBoard().getPlayer(myName);
		CardCollection myCards = me.getCards();
		CardCollection myDCards = me.getDetectivePad().getCardList();
		
		for(int i = 0; i < myDCards.countCards(); i++) {
			if(myDCards.cardAt(i).isEliminated() == false) {
				if(weapon == null && myDCards.cardAt(i).getType().equals("Weapon"))
					weapon = myDCards.cardAt(i);
				else if(weapon == null && myDCards.cardAt(i).getType().equals("Suspect"))
					suspect = myDCards.cardAt(i);
				else if(room == null && myDCards.cardAt(i).getType().equals("Room"))
					room = myDCards.cardAt(i);
			}
		}
		
		
		return accuseCorrect(suspect, weapon, room);
	}
	
	/**
	 * Check if an accusation is correct
	 */
	synchronized public boolean accuseCorrect(Card suspectCard, Card weaponCard, Card roomCard) {
		boolean result = false;
		
		Player currentPlayer = gameController.getBoard().getCurrentPlayer();
		
		System.out.println("accuseCorrect "+ suspectCard.getName() + " " +weaponCard.getName() + " " + roomCard.getName());
		
		result = gameController.getBoard().getEnvelope().containsCard(weaponCard) == true &&
		gameController.getBoard().getEnvelope().containsCard(roomCard) == true &&
		gameController.getBoard().getEnvelope().containsCard(suspectCard) == true;
		
		
		try {
			System.out.println("accuseCorrect "+ result +"\n envelope size "+ gameController.getBoard().getEnvelope().countCards() +" xml: "+gameController.getBoard().getEnvelope().toXML() );
		}
		catch(Exception e) {
			System.out.println("accuseCorrect "+ result +"\n envelope  NULL ?! "+ gameController.getBoard().getEnvelope());
		}
		return result;
	}
		
		
	public String suggest() {
		Card suspect = null;
		Card weapon = null;
		String output = "";

		Player me = gameController.getBoard().getPlayer(myName);
		CardCollection myDCards = me.getDetectivePad().getCardList();

		boolean askDisproof = true;
		
		/** List of all suspects who haven't been eliminated yet **/ 
		Vector possSuspects = new Vector();
		/** List of all weapons which haven't been eliminated yet **/
		Vector possWeapons = new Vector();

		/**
		 * Search through detective pad to find cards which aren't eliminated.
		 */
		for(int i = 0; i < myDCards.countCards(); i++)
			if(myDCards.cardAt(i).getType().equals("Suspect") && myDCards.cardAt(i).isEliminated() == false)
				possSuspects.add(myDCards.cardAt(i));
			/**
			 * Search through detective pad to find cards which aren't eliminated.
			 */
		for(int i = 0; i < myDCards.countCards(); i++)
			if(myDCards.cardAt(i).getType().equals("Weapon") && myDCards.cardAt(i).isEliminated() == false)
				possWeapons.add(myDCards.cardAt(i));

		/**
		 * Generate a random number then use that index to access the list of suspects.
		 */
		int mod = possSuspects.size();
		if(mod != 0)
			suspect = (Card)possSuspects.elementAt(  (int) (Math.random() * possSuspects.size()) % possSuspects.size()) ;
		else
			suspect = null;
		/**
		 * Generate a random number then use that index to access the list of weapons.
		 */
		mod = possWeapons.size();
		if(mod != 0)
			weapon = (Card)possWeapons.elementAt( (int) (Math.random() * possWeapons.size()) % possWeapons.size()) ;
		else
			weapon = null;
		
		/**
		 * Get the room for the suggestion by the room the player is currently standing in.
		 */
		Card room = gameController.getBoard().getCardPack().cardByName(me.getPosition().getID());
		
		
		Vector disproofInfo = findDisproveCandidate(suspect, weapon, room);
		
		Player playerCandidate = (Player)disproofInfo.firstElement();


		if(possSuspects != null)
			output += "- suspects " + possSuspects.size() + " weapons: " + possWeapons.size() + " pos " + me.getPosition().getID()+".\n";
		else
			output += "- suspects " + "NULL" + " weapons: " + "NULL" + " pos " + me.getPosition().getID()+".\n";
		if(suspect != null)
			output += "- I suspect " + suspect.getName();
		else 
			output += "- I suspect " + "NULL";
		if(weapon != null)
			output += " of using the " + weapon.getName();
		else
			output += " of using the " + "NULL";
		
		
		output += " in the " + me.getPosition().getID()+".\n";
		output += "Candidate found: " + playerCandidate;
		outputMessage(output);
		
		if(suspect != null)
			moveDefendant(suspect);
		
		
		// Send out an alert for the suggestion
		
		if(playerCandidate != null) {
			disproofReceived = false;
			Message suggestionAlertMessage = new Message();
			Vector params = new Vector();
			// params.add( new SuggestionAlert(suspect, weapon, room, me.getRealName(), playerCandidate.getRealName()) );
			params.add( new SuggestionAlert(suspect, weapon, room, me.getRealName()) );
			suggestionAlertMessage.parameters = params;
			suggestionAlertMessage.type = "suggestionAlert";
			suggestionAlertMessage.data = playerCandidate.getRealName();
			
			gameController.relayMessage(suggestionAlertMessage);
		}
		else {
			outputMessage("Nobody could disprove");
			outputMessage(" i have weap? " + myDCards.containsCard(weapon) + "\n i have room? " +  myDCards.containsCard(room) + "\n i have suspect? " + myDCards.containsCard(suspect));
			disproofReceived = false;
			
	        Message suggestionAlertMessage = new Message();
            Vector params = new Vector();
            params.add(new SuggestionAlert(suspect, weapon, room, me.getRealName()) );
            suggestionAlertMessage.parameters = params;
            suggestionAlertMessage.type = "suggestionAlert";
            suggestionAlertMessage.data = "";
            
            gameController.relayMessage(suggestionAlertMessage);
            
			askDisproof= false;
		}

		String cardShown = "";
		
		if(askDisproof == true) {
		    // Wait for a reply from the suggestion
		    
		    outputMessage("# In waiting loop");
		    while ( disproofReceived == false ) {
		        try {
		            Thread.sleep(100);
		        }
		        catch(Exception e) {
		            e.printStackTrace();
		        }
		    }
		    outputMessage("# Exit waiting loop");
		    outputMessage("Start disproof decomposition");
		    
		    DisproofAlert disproofAlert = (DisproofAlert)incomingDisproof.parameters.firstElement();
		    SuggestionAlert originalSuggestionAlert = disproofAlert.getOriginalSuggestion();
		    
		    cardShown = disproofAlert.getCardShown();
		    
		    System.out.println(" - Card shown: "+cardShown );
		}
		else {
		    cardShown = "";
		}
		
		// Figure out what to do after the suggestion

		/** A card was shown to us */
		if (!cardShown.equals("")) {
				outputMessage("I was shown a card for my suggestion, " + cardShown);
				
				// We got a good response from the suggestion, one card has been
				// eliminated.
				eliminateCard(myDCards.cardByName(cardShown));
				System.out.println("Card marked");
		}
		
		/** No card was shown to us */
		else {
	
			if (me.getCards().containsCard(suspect) == false
					&& me.getCards().containsCard(weapon) == false
					&& me.getCards().containsCard(room) == false) {
				
				output += "\nI secretly won because I know nobody else has the cards.\n";
				System.out.println(output);
				
				// At this stage we should check if we have the cards.. if not..
				// then we must know that we've found the correct cards.
				if (accuseCorrect(suspect, weapon, room) == true) {
					outputMessage("The accusation made was correct");
					Vector parameters = new Vector();
					parameters.add(me);
					gameController.directCommand("winGame", parameters);
					//dead = true;
				} else {
					outputMessage("The accusation made was WRONG..!");
					//dead = true;
				}
			} else {
				output += "Unable to disprove suggestion because I had all the cards myself.";
			}
		}

		/** So we can't suggest next turn */
		markSuggestionMade();
		return output;
	}
	
	public void markSuggestionMade() {
		Vector param = new Vector();
		param = new Vector();
		param.add(gameController.getBoard().getPlayer(myName));
		gameController.directCommand("markSuggestMade", param);
	}
		
	public void eliminateCard(Card shown) {
		Vector param = new Vector();
		param.add(shown.getName());
		gameController.directCommand("eliminateCard", param);
	}
				
				
	/**
	 * Performs the move of the player from current position to "target".
	 * 
	 */
	synchronized public void movePlayer(Position target, Vector path) {
		String output = "";
		Vector pathTaken = applyConstraint(target, path);
		System.out.println("Feed forward : " + pathTaken);
		
		Player me = gameController.getBoard().getPlayer(myName);
		output = "#movePlayer Moving to " + target.getID();
		me.setMovesRemaining(0);
		Vector param = new Vector();
		param.add(target);
		param.add(me);
		param.add(pathTaken);
		gameController.directCommand("movePlayer", param);
		
		outputMessage ( output );

	}
	

	/**
	 * 
	 * Applies constraints to the path
	 * 
	 * @param target
	 * @param path
	 * @return a new path with the constraints applied
	 */
	private Vector applyConstraint(Position target, Vector path) {
		Vector altPath = new Vector();
		
		for(int i=0;i<path.size();i++) {
			if(path.elementAt(i) == target) {
				altPath.add(path.elementAt(i));
				break;
			}
			else
				altPath.add(path.elementAt(i));
		}

		return altPath;
	}


	/**
	 * Rolls the dice and then gets the result.
	 * 
	 * @return number of both dice 
	 */
	public int rollDice() throws Exception {
		int result = 0;

		Player me = gameController.getBoard().getPlayer(myName);
		System.out.println("Sending command");
		gameController.directCommand("roll", new Vector());

		System.out.println("Command sent");
//		result = me.getMovesRemaining();
		result = 10000;
		System.out.println("Result: "  + result);

		return result;
	}
	
	/**
	 * Find a player for a disproof<p>
	 *
	 * Returns Vector<p>
	 * <li> - first element -- pointer to player with cards
	 * <li> - second element -- Vector of cards for disprove
	 * <li> - elements -- each Card
	 *
	 */
	public Vector findDisproveCandidate( Card who, Card what, Card where ) {
		Vector results = new Vector();
		
		String output = "";
		Player playerWithCard = null;
		Vector cardsFound = new Vector();
		
		Player currentPlayer = gameController.getBoard().getCurrentPlayer();

		
		// Call who into where.
		
		Player defendant = gameController.getBoard().getCharacter(who.getName());
		
		if(!currentPlayer.getCharacter().equals(who.getName())) {
			if(defendant != null) {
				Position oldRoom = defendant.getPosition();
				Position destination = currentPlayer.getPosition();

				Vector parameters = new Vector();
				parameters.add(destination);
				parameters.add(defendant);		// Who to move..
				gameController.directCommand( "movePlayer", parameters );
			}
		}
                
		// Search all players for a card.

		
		playerWithCard = null;
		cardsFound = new Vector();
		
		// Order the players so we can scan through and check who has cards in the right order.
		Vector playersToParse = localArrangePlayersBy(currentPlayer);

		for(int i = 0; i < playersToParse.size(); i++) {
			playerWithCard = (Player)playersToParse.elementAt(i);
			if(playerWithCard != currentPlayer) {
				if(playerWithCard.hasCard(who))
					cardsFound.add(who);
				if(playerWithCard.hasCard(what))
					cardsFound.add(what);
				if(playerWithCard.hasCard(where))
					cardsFound.add(where);
				if(cardsFound.size() > 0)
					break;
			}
		}
		
		if(cardsFound.size() > 0) {
			output += playerWithCard.getRealName() + " choose which card you will use to disprove :-\n ";
			for(int i = 0; i < cardsFound.size(); i++) {
				output += ((Card)cardsFound.elementAt(i)).getName() + " ";
			}
			//gameConfig.setDisproveMode(true);
		}
		else {
			output += "Unable to disprove the suggestion.";
			playerWithCard = null;
		}			
		
		outputMessage(output);
		
		results.add(playerWithCard);
		results.add(cardsFound);
		
		return results;
	}
		




	/** Order the players so we can scan through and check who has cards in the right order.
	 * 
	 */
	private Vector localArrangePlayersBy(Player currentPlayer) {
		Vector players = new Vector();
		int playerPosition = gameController.getBoard().getPlayerIndex(currentPlayer);
		Vector livePlayers = gameController.getBoard().getPlayers();
		
		// Add all elements after the player
		for(int i = playerPosition + 1; i < livePlayers.size(); i++)
			players.add(livePlayers.elementAt(i));
		// Add all elements before the player
		if(!( playerPosition == 0)) 
				for(int i = 0; i < playerPosition; i++)
					players.add(livePlayers.elementAt(i));
		return players;

	}
  
	/**
	 * Checks if a suggest was disproved or not with a given suspect and weapon.
	 * 
	 * @parameter suspect the suspect to eliminate (who did it)
	 * @parameter suspect the weapon to eliminate (what did it)
	 * 
	 * @return false - unable to disprove, true - disproved.
	 */
	public void moveDefendant(Card suspect) {
		Player defendant = gameController.getBoard().getCharacter(suspect.getName());
		Player me = gameController.getBoard().getPlayer(myName);

		Card testCard = null;
		Card room = gameController.getBoard().getCardPack().cardByName(me.getPosition().getID());
		if(defendant != null) {
			String output = "";
	        	
			if(defendant != null) {
				Position oldRoom = defendant.getPosition();
				Position destination = me.getPosition();
				
				Vector parameters = new Vector();
				parameters.add(destination);	// Where to move
				parameters.add(defendant);		// Who to move there
				gameController.directCommand( "movePlayer", parameters );
			}
                	
        		output = "Called " + defendant.getRealName() + " into the " + room.getName();
        	
			outputMessage(output);
		}
	}


	public boolean inDeadlock() {
		boolean result = false;
		Position myPosition = gameController.getBoard().getCurrentPlayer().getPosition();
		Vector N = myPosition.getNeighbours();
		int neighbours = 0;
		int free = 0;
		
		for(int i=0;i<N.size();i++) {
			Position n = (Position) (N.get(i));
			if(n instanceof Square || n instanceof StartingSquare ) {
				if(n.isOccupied() == false) {
					free++;
				}
			}
			else {
				free++;
			}
			neighbours++;
		}
		
		result = (free == 0);
		
		return result;
	}


	/* (non-Javadoc)
	 * @see Cluedo.API.AIContract#movePlayer(Cluedo.Game.Position, java.util.Vector, int)
	 */
	public void movePlayer(Position target, Vector path, int amount) {
		// TODO Auto-generated method stub
		
	}

	
 }
